#!/usr/bin/env node

'use strict';

const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const vm = require('vm');
const moment = require('dayjs');
const lodash = require('lodash');
const repl = require('repl');
const crypto = require('crypto');
const utils = require('./js/utils');
const parser = require('cron-parser');
const shell = require('shelljs');
const express = require("express");
const bodyParser = require("body-parser");
const temme = require("temme").default;
let trapProcess, startTime, uuid_index = 0;

moment.extend((option, dayjsClass, dayjsFactory) => {
	dayjsClass.prototype.ds = function () {
		return this.format('YYYY-MM-DD');
	}
	dayjsClass.prototype.ts = function () {
		return this.format('YYYY-MM-DD HH:mm:ss');
	}
});

function exec(cmd) {
	const encoding = "gb2312";
	const options = { async: true, silent: true };
	const command = shell.exec(cmd, options);
	const { stdout, stderr } = command;
	stdout.on("data", (data) => {
		// console.log(require("iconv-lite").decode(Buffer.from(data, "binary"), encoding));
	});
	stderr.on("data", (data) => {
		// console.error(require("iconv-lite").decode(Buffer.from(data, "binary"), encoding));
	});
	return command;
}
function showCaptureTime() {
	if (trapProcess) {
		process.stdout.write(`${lodash.repeat('\b', 20)}${utils.timeConversion(moment().diff(startTime, 's'))}          `);
		setTimeout(showCaptureTime, 1000);
	}
}

const args = process.argv.slice(2);

if (args[0] === '-h' || args[0] === '--help') {
	console.log('xn [jsfile]');
	process.exit(0);
}
if (args[0]) {
	let file = args[0];
	if (!path.extname(file) && !fs.existsSync(file)) {
		file = `${file}.js`;
	}
	const info = fs.statSync(file);
	if (info.isDirectory()) {
		file = path.join('index.js');
	}
	if (!fs.existsSync(file)) {
		console.log(`${file} 不存在`);
		process.exit(0);
	}
	vm.runInNewContext('(async function(){' + fs.readFileSync(file, 'utf-8').split(/\r|\n|\r\n/).filter(o => !/^\s*\/\//.test(o)).join('\n') + '})()', {
		__dirname: path.dirname(path.resolve(file)),
		require: (m) => /^\.[\/\\]/.test(m) ? require(path.resolve(path.dirname(path.resolve(file)), m)) : require(m),
		console,
		process,
		global,
		_: lodash,
		moment,
		fs,
		path,
		os,
		utils,
		shell,
		crypto,
		express,
		bodyParser,
		temme
	});
} else {
	let lastLine;
	const historyPath = path.join(os.homedir(), `.xn_history/.xn_repl_history`);
	const deleteLeft = repl.REPLServer.prototype.__proto__._deleteLeft;
	const insertString = repl.REPLServer.prototype.__proto__._insertString;
	const addHistory = repl.REPLServer.prototype.__proto__._addHistory;
	const newLine = repl.REPLServer.prototype.__proto__._line;
	const ttyWrite = repl.REPLServer.prototype.__proto__._ttyWrite;

	repl.REPLServer.prototype.__proto__._deleteLeft = function () {
		deleteLeft.call(this);
		lastLine = this.line;
		this.historyIndex = -1;
	};
	repl.REPLServer.prototype.__proto__._insertString = function (c) {
		insertString.call(this, c);
		lastLine = this.line;
		this.historyIndex = -1;
	};
	repl.REPLServer.prototype.__proto__._addHistory = function () {
		const line = this.line.trim();
		if (line === 'q' || line === 'exit') {
			return line;
		}
		return addHistory.call(this);
	};
	repl.REPLServer.prototype.__proto__._line = function () {
		lastLine = undefined;
		newLine.call(this);
	};
	repl.REPLServer.prototype.__proto__._ttyWrite = function (s, key = {}) {
		if (!(key.ctrl || key.meta)) {
			if (key.name === 'up' || key.name === 'down') {
				const history = this.history;
				if (lastLine) {
					const reg = new RegExp(`^${lastLine}`);
					this.history = history.filter(o => reg.test(o));
				}
				if (key.name === 'up') {
					this._historyPrev();
				} else if (key.name === 'down') {
					this._historyNext();
				}
				this.history = history;
				return;
			}
		}
		ttyWrite.call(this, s, key);
	};

	const server = repl.start({
		prompt: 'node > ',
		// allow strict mode via environment variable
		preview: false,
		replMode: (process.env.NODE_REPL_MODE === 'strict' || process.argv.indexOf('--use_strict') !== -1) ? repl.REPL_MODE_STRICT : repl.REPL_MODE_MAGIC
	});
	fs.createFileSync(historyPath);
	server.removeHistoryDuplicates = true;
	server.historySize = 10000;
	server.history = fs.readFileSync(historyPath, 'utf8').toString().split('\n').filter(o => o && o.trim());

	Object.defineProperty(server.context, '_', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return lodash;
		}
	});

	Object.defineProperty(server.context, 'fs', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return fs;
		}
	});

	Object.defineProperty(server.context, 'moment', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return moment;
		}
	});

	if (process.argv[2] === 'tf') {
		const tf = require('@tensorflow/tfjs-node');
		Object.defineProperty(server.context, 'tf', {
			'configurable': true,
			'enumerable': false,
			'get': function () {
				return tf;
			}
		});
	}

	Object.defineProperty(server.context, 'shell', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return shell;
		}
	});

	// log
	Object.defineProperty(server.context, 'log', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) { console.log(...args); };
		}
	});

	// md5
	Object.defineProperty(server.context, 'md5', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (text) {
				const hash = crypto.createHash('md5');
				hash.update(text);
				return hash.digest('hex');
			};
		}
	});

	// help_expr
	Object.defineProperty(server.context, 'help_expr', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return console.log('前瞻断言: (?=exp) 顺序肯定环视，表示所在位置右侧能够匹配exp\n\t  (?!exp) 顺序否定环视，表示所在位置右侧不能匹配exp\n后瞻断言: (?<=exp) 逆序肯定环视，表示所在位置左侧能够匹配exp\n\t  (?<!exp) 逆序否定环视，表示所在位置左侧不能匹配exp');
			;
		}
	});

	// base64
	Object.defineProperty(server.context, 'decodeBase64', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (text) {
				return Buffer.from(text, 'base64').toString('utf-8');
			};
		}
	});
	Object.defineProperty(server.context, 'encodeBase64', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (text) {
				return Buffer.from(text, 'utf-8').toString('base64');
			};
		}
	});
	Object.defineProperty(server.context, 'isBase64', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (text) {
				return !!text && (text === encodeBase64(decodeBase64(o)));
			};
		}
	});

	// checkIdNo
	Object.defineProperty(server.context, 'idNo', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) { utils.checkIdNo(...args); };
		}
	});

	// decodeURI()
	Object.defineProperty(server.context, 'decodeURI', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) { return decodeURI(args[0]); };
		}
	});

	// uuid()
	Object.defineProperty(server.context, 'uuid', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) {
				var s = [];
				var hexDigits = "0123456789abcdef";
				for (var i = 0; i < 9; i++) {
					s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
				}
				var uniq = uuid_index.toString(16);
				while (uniq.length < 4) uniq = `0${uniq}`;
				uuid_index++;
				return Date.now().toString(16) + s.join('') + uniq;
			};
		}
	});

	// hex()
	Object.defineProperty(server.context, 'hex', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) {
				return Number(args[0]).toString(16);
			};
		}
	});

	// temme(html, selector)
	Object.defineProperty(server.context, 'temme', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) {
				return temme(args[0], args[1]);
			};
		}
	});

	function capture(file) {
		setTimeout(function () {
			trapProcess = exec(`ffmpeg -y -f dshow -i audio="麦克风阵列 (适用于数字麦克风的英特尔® 智音技术)" -f dshow -i audio="virtual-audio-capturer" -filter_complex amix=inputs=2:duration=first:dropout_transition=0 -f dshow -i video="screen-capture-recorder" -i D:/logo.png -filter_complex \"overlay=W-w-5:H-h-85\" -pix_fmt yuv420p "${file}"`);
			startTime = moment();
			setTimeout(showCaptureTime, 1000);
		}, 3000);
		return `3秒后开始录制桌面到 ${file}, 按 ctrl+c 结束录制`;
	}
	// capture
	Object.defineProperty(server.context, 'capture', {
		'configurable': true,
		'enumerable': false,
		'get': capture
	});

	function schedule(line, n = 10) {
		line = line.trim().split(/\s+/);
		lodash.keys(Array(6)).forEach(o => line[o] === undefined && (line[o] = '*'));
		line = line.join(' ');
		const interval = parser.parseExpression(line);
		console.log(`运行 ${line} 的结果如下：`);
		for (let i = 0; i < n; ++i) {
			console.log(moment(interval.next()._date.ts).format('YYYY-MM-DD HH:mm:ss'));
		}
	}

	// checkSchedule
	Object.defineProperty(server.context, 'schedule', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return schedule;
		}
	});

	function say(line) {
		const iconv = require('iconv-lite');
		exec(`powershell.exe Add-Type -AssemblyName System.speech; $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer; $speak.Rate = 1; $speak.Speak([Console]::In.ReadLine()); exit`).stdin.end(iconv.encode(line, 'gbk'));
	}

	// say: 文字转语音
	// 网页版：window.speechSynthesis.speak(new SpeechSynthesisUtterance('方运江'))
	Object.defineProperty(server.context, 'say', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			return function (...args) {
				say(args[0]);
				return '已播放...';
			};
		}
	});

	// 定义math的函数
	['abs', 'ceil', 'floor', 'round', 'min', 'max', 'sqrt', 'pow', 'exp', 'sin', 'cos', 'tan'].forEach(o => {
		Object.defineProperty(server.context, o, {
			'configurable': true,
			'enumerable': false,
			'get': function () {
				return Math[o];
			}
		});
	});

	// help
	Object.defineProperty(server.context, 'h', {
		'configurable': true,
		'enumerable': false,
		'get': function () {
			console.log('_');
			console.log('moment');
			console.log('shell');
			console.log('encodeBase64(...)');
			console.log('decodeBase64(...)');
			console.log('isBase64(...)');
			console.log('decodeURI(...)');
			console.log('md5(...)');
			console.log('log(...)');
			console.log('idNo(...)');
			console.log('uuid(...)');
			console.log('hex(...): 16进制转换');
			console.log('help_expr: 查看正则表达式帮助');
			console.log('temme(html, selector)');
			console.log('schedule(\'0 0 * * * *\', n)|s \'0 0 * * * *\' [n]');
			console.log('capture(\'xx.mp4\')|c xx.mp4');
			console.log('say(\'xxx\') | say xxx: 播放语音');
		}
	});

	const events = server._events;
	events.line = lodash.wrap(events.line, function (cmd, line) {
		line = line.trim();
		if (line === 'q' || line === 'exit') {
			if (trapProcess) {
				trapProcess.stdin.write('q');
				trapProcess = null;
				console.log('录制完成');
				return this.prompt(true);
			}
			return this.close();
		}
		if (line === 'cls') {
			shell.exec('printf "\\33c\\e[3J"');
			return this.prompt(true);
		}
		if (/_\s*=|moment\s*=/.test(line)) {
			console.error('can not change const variable');
			line = '';
		}
		const list = utils.parseLine(line);
		if (list[0] === 's') {
			if (!list[1]) {
				line = ''
			} else {
				schedule(list[1], list[2]);
				this.prompt(true);
				return;
			}
		} else if (list[0] === 'c') {
			if (!list[1]) {
				line = ''
			} else {
				const info = capture(list[1]);
				console.log(info);
				this.prompt(true);
				return;
			}
		} else if (list[0] === 'say') {
			if (!list[1]) {
				line = ''
			} else {
				say(list.slice(1).join(' '));
				this.prompt(true);
				return;
			}
		}
		cmd(line);
	});
	events.SIGINT = function () {
		if (trapProcess) {
			trapProcess.stdin.write('q');
			trapProcess = null;
			console.log('录制完成');
			this.prompt(true);
		} else {
			this.clearLine();
		}
		this.prompt(true);
	};
	events.close = function () {
		fs.writeFileSync(historyPath, this.history.join('\n'));
		console.log('bye');
		process.exit(0);
	};
}
